#include "stdafx.h"
#include "HXRender.h"
#include "HXWindow.h"

#pragma warning(disable : 4005)

// OpenGL Header Files:
#include "GL/wglew.h"
#include "GL/glu.h"

//-----------------------------------------------------------------------------

//! Constructor
HXRenderData::HXRenderData()
: m_isMorphed(0),
  m_oglTexArray(NULL),
  m_oglTexSize(0),
  m_built(0),
  m_center(false),
  m_recompNormal(1),
  m_normalFrameCounter(1),
  m_VertexShaderObject(0), 
  m_PixelShaderObject(0), 
  m_ProgramObject(0)
{
	InitializeCriticalSectionAndSpinCount(&m_criticalSection_Draw, 0x80000400);
}

//! Destructor
HXRenderData::~HXRenderData()
{
	if (m_oglTexSize != 0)
    {
	    glDeleteTextures(m_oglTexSize, m_oglTexArray);
		delete [] m_oglTexArray;
    }
	DeleteCriticalSection(&m_criticalSection_Draw);
}

//! Copy constructor
HXRenderData::HXRenderData(HXRenderData const& a_rOther)
{
	m_renderModel = a_rOther.m_renderModel;
	m_vertexListA = a_rOther.m_vertexListA;
	m_vertexListB = a_rOther.m_vertexListB;
	m_oglTexArray = NULL;
	m_oglTexSize = 0;
    m_normList = a_rOther.m_normList;
	m_isMorphed = a_rOther.m_isMorphed;
	m_built = a_rOther.m_built & ~HXR_BUILT_SHADERS;
	m_center = a_rOther.m_center;
	m_recompNormal = a_rOther.m_recompNormal;
	m_normalFrameCounter = 1;
	m_morph = a_rOther.m_morph;
	m_VertexShaderObject = 0;
	m_PixelShaderObject = 0;
	m_ProgramObject = 0;
	InitializeCriticalSectionAndSpinCount(&m_criticalSection_Draw, 0x80000400);
}

//! Calculate and store vertex normals.
void HXRenderData::BuildNormalList()
{
	vector<HXVector3F>& vertexCache = VertexBuffer();

    // Calculate the vertex normals.
    HXVector3F zero;
    m_normList.resize(Model().modelVertexSize);
    for (size_t v = 0; v < m_normList.size(); v++)
        m_normList[v] = zero;
    for (size_t t = 0; t < Model().triangleList.size(); t++)
    {
        HXVectorTriangle triFacet = Model().triangleList[t];
        HXVector3F v1 = vertexCache[triFacet[0]];
        HXVector3F v2 = vertexCache[triFacet[1]];
        HXVector3F v3 = vertexCache[triFacet[2]];
        HXVector3F cross = Cross(v2 - v1, v3 - v1);
        float ln = cross.Length();
        HXVector3F norm;
        if (ln != 0) 
			norm = cross / ln;
        m_normList[triFacet[0]] += norm;
        m_normList[triFacet[1]] += norm;
        m_normList[triFacet[2]] += norm;
    }
    for (size_t q = 0; q < Model().quadList.size(); q++)
    {
        HXVectorQuad quadFacet = Model().quadList[q];
        HXVector3F v0 = vertexCache[quadFacet[0]];
        HXVector3F v1 = vertexCache[quadFacet[1]];
        HXVector3F v2 = vertexCache[quadFacet[2]];
        HXVector3F v3 = vertexCache[quadFacet[3]];
        HXVector3F cross = HXVector3F(
                    (v0[1]-v1[1]) * (v0[2]+v1[2]) +
                    (v1[1]-v2[1]) * (v1[2]+v2[2]) +
                    (v2[1]-v3[1]) * (v2[2]+v3[2]) +
                    (v3[1]-v0[1]) * (v3[2]+v0[2]),
                    (v0[2]-v1[2]) * (v0[0]+v1[0]) +
                    (v1[2]-v2[2]) * (v1[0]+v2[0]) +
                    (v2[2]-v3[2]) * (v2[0]+v3[0]) +
                    (v3[2]-v0[2]) * (v3[0]+v0[0]),
                    (v0[0]-v1[0]) * (v0[1]+v1[1]) +
                    (v1[0]-v2[0]) * (v1[1]+v2[1]) +
                    (v2[0]-v3[0]) * (v2[1]+v3[1]) +
                    (v3[0]-v0[0]) * (v3[1]+v0[1]) );
        float ln = cross.Length();
        HXVector3F norm;
        if (ln != 0)
            norm = cross / ln;
        m_normList[quadFacet[0]] += norm;
        m_normList[quadFacet[1]] += norm;
        m_normList[quadFacet[2]] += norm;
        m_normList[quadFacet[3]] += norm;
    }

	// seam fix
	if (Model().vertexAdded.size())
	{
		size_t originalVertexSize = Model().modelVertexSize - Model().vertexAdded.size();
		//list<HXVector3F> tempNorms;
		for (size_t a = 0; a < Model().vertexAdded.size(); a++)
		{
			//tempNorms.push_back(m_normList[Model().vertexAdded[a]]);
			m_normList[Model().vertexAdded[a]] += m_normList[originalVertexSize+a];
		}
		for (size_t a = 0; a < Model().vertexAdded.size(); a++)
		{
			m_normList[originalVertexSize+a] = m_normList[Model().vertexAdded[a]];
		}
		/*size_t o = originalVertexSize;
		for (list<HXVector3F>::iterator tnIt = tempNorms.begin(); tnIt != tempNorms.end(); ++tnIt)
		{
			m_normList[o++] += *tnIt;
		}*/
	}

    for (size_t v = 0; v < m_normList.size(); v++)
    {
        float ln = m_normList[v].Length();
        if (ln > 0)
            m_normList[v] = m_normList[v] / ln;
    }

	m_built |= HXR_BUILT_NORMALS;
}

//! Render any parts of the model that are more opaque than the alpha_threshold as completely opaque.
void HXRenderData::DrawOpaque(HXShader* pShader)
{
	if (m_renderModel.alphaThreshold > 0.0f)
	{
		glEnable(GL_ALPHA_TEST);
		glAlphaFunc(GL_GEQUAL, 1.0f/*m_renderModel.alphaThreshold*/);
		Draw(pShader);
		glDisable(GL_ALPHA_TEST);
	}
	else
	{
		Draw(pShader);
	}

}

//! Render any parts of the model that are less opaque than the alpha_threshold as partially transparent.
void HXRenderData::DrawTransparent(HXShader* pShader)
{
	if (m_renderModel.alphaThreshold > 0.0f) // >0, pointless to continue otherwise
	{
		glEnable(GL_BLEND);
		//glBlendFunc(GL_ONE_MINUS_DST_ALPHA,GL_DST_ALPHA);
		//glBlendColor(0.0,0.0,0.0,1.0);
		if (pShader) // OpenGL2.0
		{
			glBlendEquationSeparate(GL_FUNC_ADD,GL_MAX);
			glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_SRC_ALPHA,GL_DST_ALPHA);
		}
		else
		{
			glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
		}
		glEnable(GL_ALPHA_TEST);
		glAlphaFunc(GL_LESS, 1.0f/*m_renderModel.alphaThreshold*/);
		glEnable( GL_CULL_FACE );
		glDepthMask(0); // Don't write to the depth buffer
		glCullFace( GL_FRONT );
		Draw(pShader);
		glCullFace( GL_BACK );
		Draw(pShader);
		glDepthMask(1);
		glDisable( GL_CULL_FACE );
		glDisable(GL_ALPHA_TEST);

		glDisable(GL_BLEND);

		/*glEnable(GL_BLEND);
		glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
		glEnable(GL_ALPHA_TEST);
		glAlphaFunc(GL_LESS, m_renderModel.alphaThreshold);
		glDepthMask(0); // Don't write to the depth buffer
		Draw();
		glDepthMask(1);
		glDisable(GL_ALPHA_TEST);
		glDisable(GL_BLEND);*/
	}
}

//! Set OpenGL render material.
void HXRenderData::SetMaterial(HXMaterial& matter, HXShader* pShader)
{
	if (matter.texture.IsEmpty())
	{
		if (pShader)
		{
			pShader->BindTexture2D("SurfaceTex", 0, 2);
			pShader->SetFloat("texBlend", 0.0f);
		}
		else
		{
			glDisable(GL_TEXTURE_2D);
		}
	}
	else
	{
		if (pShader)
		{
			pShader->BindTexture2D("SurfaceTex", matter.oglTextureName, 2/*texunit*/);
			pShader->SetFloat("texBlend", 1.0f);
		}
		else
		{
			glEnable(GL_TEXTURE_2D);
			glBindTexture(GL_TEXTURE_2D, matter.oglTextureName);
		}
	}
	GLfloat ambientColor[] = {matter.ambient[0], matter.ambient[1], matter.ambient[2], 1.0f};
	GLfloat diffuseColor[] = {matter.diffuse[0], matter.diffuse[1], matter.diffuse[2], 1.0f};
	GLfloat specularColor[] = {matter.specular[0], matter.specular[1], matter.specular[2], 1.0f};
	glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, ambientColor);
    glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, diffuseColor);
	glMaterialfv(GL_FRONT, GL_SPECULAR, specularColor);
	glMaterialf(GL_FRONT, GL_SHININESS, matter.shininess);
}

//! Reset OpenGL render material to default.
void HXRenderData::ResetMaterial()
{
	glDisable(GL_TEXTURE_2D);
	GLfloat materialColour[] = {0.7f, 0.7f, 0.7f, 1.0f};
	glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, materialColour);
	glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, materialColour);
}

//! Render the model.
void HXRenderData::Draw(HXShader* pShader)
{
	EnterCriticalSection(&m_criticalSection_Draw);

	glShadeModel(GL_SMOOTH);
    glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);

	if (Model().textureMode != HXTM_INVALID)
	{
		if (!g_config.bImmediateModeRender && (Model().textureMode != HXTM_PERFACET_MULTICOORD)) // normal rendering
		{
			glEnableClientState(GL_VERTEX_ARRAY);
			glEnableClientState(GL_NORMAL_ARRAY);
			//glVertexPointer(3, GL_FLOAT, 0, &m_renderModel.vertexList[0]);
			glVertexPointer(3, GL_FLOAT, 0, &VertexBuffer()[0]);
			glNormalPointer(GL_FLOAT, 0, &m_normList[0]);
			if (Model().textureMode != HXTM_NOTEXTURE)
			{
				glEnableClientState(GL_TEXTURE_COORD_ARRAY);
				if (Model().textureMode == HXTM_PERFACET_SINGLECOORD)
				{
					glTexCoordPointer(2, GL_FLOAT, 0, &Model().expandedTextureList[0]);
				}
				else // per vertex
				{
					glTexCoordPointer(2, GL_FLOAT, 0, &Model().textureList[0]);
				}
			}
			if (Model().quadList.size())
			{
				unsigned int* pArray = reinterpret_cast<unsigned int*>(&Model().quadList[0]);
				if (Model().materialQuadSegments.size())
				{
					for (size_t i = 0; i < Model().materialQuadSegments.size()-1; i++)
					{
						SetMaterial(Model().materialList[Model().materialQuadList[Model().materialQuadSegments[i]]], pShader);
						glDrawElements(	GL_QUADS, 
										4*(Model().materialQuadSegments[i+1] - Model().materialQuadSegments[i]),
										GL_UNSIGNED_INT, 
										pArray+4*Model().materialQuadSegments[i] );
					}
				}
				else
				{
					ResetMaterial();
					glDrawElements(	GL_QUADS, 
									4*Model().quadList.size(),
									GL_UNSIGNED_INT, 
									pArray );
				}
			}
			if (Model().triangleList.size())
			{
				unsigned int* pArray = reinterpret_cast<unsigned int*>(&Model().triangleList[0]);
				if (Model().materialTriangleSegments.size())
				{
					for (size_t i = 0; i < Model().materialTriangleSegments.size()-1; i++)
					{
						SetMaterial(Model().materialList[Model().materialTriangleList[Model().materialTriangleSegments[i]]], pShader);
						glDrawElements(	GL_TRIANGLES, 
										3*(Model().materialTriangleSegments[i+1] - Model().materialTriangleSegments[i]),
										GL_UNSIGNED_INT, 
										pArray+3*Model().materialTriangleSegments[i] );
					}
				}
				else
				{
					ResetMaterial();
					glDrawElements(	GL_TRIANGLES, 
									3*Model().triangleList.size(),
									GL_UNSIGNED_INT, 
									pArray );
				}
				
			}
			if (Model().textureMode != HXTM_NOTEXTURE)
			{
				glDisableClientState(GL_TEXTURE_COORD_ARRAY);
			}
			glDisableClientState(GL_NORMAL_ARRAY);
			glDisableClientState(GL_VERTEX_ARRAY);
		}
		else // immediate mode rendering
		{
			vector<HXVector3F>& vertexCache = VertexBuffer();
			int lastMaterial = 0;
			if (Model().materialList.size())
			{
				SetMaterial(Model().materialList[lastMaterial], pShader);
			}
			else
			{
				ResetMaterial();
			}

			glBegin(GL_QUADS);
			for (size_t q = 0; q < Model().quadList.size(); q++)
			{
				HXVectorQuad& quad = Model().quadList[q];
				HXVectorQuad& tex = Model().textureQuadList[q];

				if (lastMaterial != Model().materialQuadList[q])
				{
					glEnd();
					lastMaterial = Model().materialQuadList[q];
					SetMaterial(Model().materialList[lastMaterial], pShader);
					glBegin(GL_QUADS);
				}

				glNormal3fv(m_normList[quad[0]].GetData());
				glTexCoord2fv(Model().textureList[tex[0]].GetData());
				glVertex3fv(vertexCache[quad[0]].GetData());

				glNormal3fv(m_normList[quad[1]].GetData());
				glTexCoord2fv(Model().textureList[tex[1]].GetData());
				glVertex3fv(vertexCache[quad[1]].GetData());

				glNormal3fv(m_normList[quad[2]].GetData());
				glTexCoord2fv(Model().textureList[tex[2]].GetData());
				glVertex3fv(vertexCache[quad[2]].GetData());

				glNormal3fv(m_normList[quad[3]].GetData());
				glTexCoord2fv(Model().textureList[tex[3]].GetData());
				glVertex3fv(vertexCache[quad[3]].GetData());
			}
			glEnd();

			glBegin(GL_TRIANGLES);
			for (size_t t = 0; t < Model().triangleList.size(); t++)
			{
				HXVectorTriangle& tri = Model().triangleList[t];
				HXVectorTriangle& tex = Model().textureTriangleList[t];

				if (lastMaterial != Model().materialTriangleList[t])
				{
					glEnd();
					lastMaterial = Model().materialTriangleList[t];
					SetMaterial(Model().materialList[lastMaterial], pShader);
					glBegin(GL_TRIANGLES);
				}

				glNormal3fv(m_normList[tri[0]].GetData());
				glTexCoord2fv(Model().textureList[tex[0]].GetData());
				glVertex3fv(vertexCache[tri[0]].GetData());

				glNormal3fv(m_normList[tri[1]].GetData());
				glTexCoord2fv(Model().textureList[tex[1]].GetData());
				glVertex3fv(vertexCache[tri[1]].GetData());

				glNormal3fv(m_normList[tri[2]].GetData());
				glTexCoord2fv(Model().textureList[tex[2]].GetData());
				glVertex3fv(vertexCache[tri[2]].GetData());

			}
			glEnd();
			
		}
	}

	LeaveCriticalSection(&m_criticalSection_Draw);
}

//! Update texture data with new texture.
void HXRenderData::UpdateTextures(bool bMipmaps)
{
	if (m_oglTexSize)
	{
		glEnable(GL_TEXTURE_2D);
		size_t texIndex = 0;
		for (size_t i = 0; i < Model().materialList.size(); i++)
		{
			if (!Model().materialList[i].texture.IsEmpty())
			{
				glBindTexture(GL_TEXTURE_2D, m_oglTexArray[texIndex]);
				GLint val = 0;
				glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &val);
				if (Model().materialList[i].texture.GetWidth() == static_cast<size_t>(val))
				{
					glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &val);
					if (Model().materialList[i].texture.GetHeight() == static_cast<size_t>(val))
					{
						glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, Model().materialList[i].texture.GetWidth(), Model().materialList[i].texture.GetHeight(), GL_RGBA, GL_UNSIGNED_BYTE, Model().materialList[i].texture.GetData());
						//int oglError = glGetError();
						//if (oglError) {}
						return;
					}
				}
				texIndex++;
			}
		}
	}
	BuildTextures(bMipmaps);
}

//! Prepare texture data for rendering.
void HXRenderData::BuildTextures(bool bMipmaps)
{
	ClearTextures();

	m_oglTexSize = Model().textureCount;
	m_oglTexArray = new GLuint[m_oglTexSize];
    glEnable(GL_TEXTURE_2D);
    glGenTextures(m_oglTexSize, (GLuint*)m_oglTexArray);
	size_t texIndex = 0;
	for (size_t i = 0; i < Model().materialList.size(); i++)
	{
		if (!Model().materialList[i].texture.IsEmpty())
		{
			Model().materialList[i].oglTextureName = m_oglTexArray[texIndex];
			glBindTexture(GL_TEXTURE_2D, m_oglTexArray[texIndex]);
			glPixelStorei(GL_UNPACK_ALIGNMENT,8);
			glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_CLAMP_TO_EDGE);
			glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_CLAMP_TO_EDGE);
			glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
			glTexEnvf(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_MODULATE);
			if (bMipmaps)
			{
				glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
				glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_TRUE);
				glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, Model().materialList[i].texture.GetWidth(), Model().materialList[i].texture.GetHeight(), 0, GL_RGBA, GL_UNSIGNED_BYTE, Model().materialList[i].texture.GetData());
			}
			else
			{
				glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
				glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, Model().materialList[i].texture.GetWidth(), Model().materialList[i].texture.GetHeight(), 0, GL_RGBA, GL_UNSIGNED_BYTE, Model().materialList[i].texture.GetData());
			}
			texIndex++;
		}
	}
	m_built |= HXR_BUILT_TEXTURES;
}

void HXRenderData::ClearTextures()
{
	if (m_oglTexArray != NULL)
    {
	    glDeleteTextures(m_oglTexSize, (GLuint*)m_oglTexArray);
		delete [] m_oglTexArray;
        m_oglTexArray = NULL;
    }
}

void HXRenderData::LoadShaders(const wstring& pshaderFile, const wstring& vshaderFile)
{
	m_shader.Destroy();
	m_shader.AttachFragmentShaderFromFile(pshaderFile);
	m_shader.AttachVertexShaderFromFile(vshaderFile);
}

bool HXRenderData::BuildShaders()
{	
	if (m_shader.Link())
	{
		m_built |= HXR_BUILT_SHADERS;
		return true;
	}
	m_built &= ~HXR_BUILT_SHADERS;
	return false;
}

void HXRenderData::StartShaders()
{
	m_shader.Bind();
	//if (m_ProgramObject) glUseProgram(m_ProgramObject);
}

void HXRenderData::EndShaders()
{
	m_shader.Unbind();
	//if (m_ProgramObject) glUseProgram(0);
}

//! Retrieve available morphs from model.
void HXRenderData::CreateMorphs()
{
	m_morph.CreateFrom(m_renderModel);
	m_isMorphed = 0;
}

//! Apply any changes to the morph values to the 3D model.
void HXRenderData::ApplyMorphs() 
{
	if (m_morph.morphAdjustableCount) // there's something to morph
	{
		// apply morphs
		vector<HXVector3F>& outVertex = ((m_isMorphed == 1) ? m_vertexListB : m_vertexListA);
		// initialize the output vertex to the pre-morph state
		if (outVertex.size() != m_renderModel.modelVertexSize) outVertex.resize(m_renderModel.modelVertexSize);
		if (m_renderModel.modelVertexSize > 0) 
		{
			for (size_t i = 0; i < outVertex.size(); i++)
			{
				outVertex[i] = m_renderModel.vertexList[i];
			}
		}
		// apply the diff morph
		for (size_t i = 0; i < m_renderModel.diffMorphList.size(); i++)
		{
			if (m_morph.diffMorphVals[i] == 0) continue;
			if ((m_renderModel.diffMorphList[i].vertexDeltaList.size() < outVertex.size())) 
			{
				continue;
			}
			for (size_t j = 0; j < outVertex.size(); j++)
			{
				outVertex[j] += (m_renderModel.diffMorphList[i].vertexDeltaList[j] * m_morph.diffMorphVals[i]);
			}
		}
		// apply the stat morph
		for (size_t i = 0; i < m_renderModel.statMorphList.size(); i++)
		{
			if (m_morph.statMorphVals[i] == 0) continue;
			unsigned long firstOffset = m_renderModel.statMorphList[i].firstVertexOffset;
			unsigned long statVertexCount = m_renderModel.statMorphList[i].vertexIdxList.size();
			if ((firstOffset + statVertexCount) > m_renderModel.vertexList.size()) continue;
			float s = m_morph.statMorphVals[i];
			for (size_t j = 0; j < statVertexCount; j++)
			{
				unsigned long vertex = m_renderModel.statMorphList[i].vertexIdxList[j];
				outVertex[vertex] += ((m_renderModel.vertexList[firstOffset + j] - m_renderModel.vertexList[vertex]) * s);
			}
		}
		EnterCriticalSection(&m_criticalSection_Draw);
		switch (m_isMorphed)
		{
		case 0:
			m_isMorphed = 1;
			break;
		case 1:
			m_isMorphed = 2;
			break;
		case 2:
			m_isMorphed = 1;
			break;
		}
		LeaveCriticalSection(&m_criticalSection_Draw);
		switch (RecompNormal())
		{
		case 0:
			break;
		case 1:
			BuildNormalList(); // need to update normals after deforming face
		default:
			if (m_normalFrameCounter >= RecompNormal())
			{
				BuildNormalList();
				m_normalFrameCounter = 0;
			}
			m_normalFrameCounter++;
			break;
		}
	}
}

//-----------------------------------------------------------------------------